package io.github.mthli.Ninja.View; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.view.*; import io.github.mthli.Ninja.Unit.ViewUnit; public class SwipeToBoundListener implements View.OnTouchListener { public interface BoundCallback { boolean canSwipe(); void onSwipe(); void onBound(boolean canSwitch, boolean left); } private View view; private BoundCallback callback; private int targetWidth = 1; private int slop; private long animTime; private float downX; private float translationX; private boolean swiping; private boolean swipingLeft; private boolean canSwitch; private int swipingSlop; private VelocityTracker velocityTracker; public SwipeToBoundListener(View view, BoundCallback callback) { this.view = view; this.callback = callback; ViewConfiguration configuration = ViewConfiguration.get(this.view.getContext()); this.slop = configuration.getScaledTouchSlop(); this.animTime = this.view.getContext().getResources().getInteger(android.R.integer.config_shortAnimTime); this.swiping = false; this.swipingLeft = false; this.canSwitch = false; } @Override public boolean onTouch(View v, MotionEvent event) { if (!callback.canSwipe()) { return false; } event.offsetLocation(translationX, 0); if (targetWidth < 2) { targetWidth = view.getWidth(); } switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: { downX = event.getRawX(); velocityTracker = VelocityTracker.obtain(); velocityTracker.addMovement(event); return false; } case MotionEvent.ACTION_UP: { if (velocityTracker == null) { break; } velocityTracker.addMovement(event); velocityTracker.computeCurrentVelocity(1000); if (swiping) { view.animate() .translationX(0f) .setDuration(animTime) .setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { callback.onBound(canSwitch, swipingLeft); } }); } downX = 0; translationX = 0; swiping = false; velocityTracker.recycle(); velocityTracker = null; break; } case MotionEvent.ACTION_CANCEL: { if (velocityTracker == null) { break; } view.animate() .translationX(0f) .setDuration(animTime) .setListener(null); downX = 0; translationX = 0; swiping = false; velocityTracker.recycle(); velocityTracker = null; break; } case MotionEvent.ACTION_MOVE: { if (velocityTracker == null) { break; } velocityTracker.addMovement(event); float deltaX = event.getRawX() - downX; if (Math.abs(deltaX) > slop) { swiping = true; swipingLeft = deltaX < 0; canSwitch = Math.abs(deltaX) >= ViewUnit.dp2px(view.getContext(), 48); // Can switch tabs when deltaX >= 48 to prevent misuse swipingSlop = (deltaX > 0 ? slop : -slop); view.getParent().requestDisallowInterceptTouchEvent(true); MotionEvent cancelEvent = MotionEvent.obtainNoHistory(event); cancelEvent.setAction(MotionEvent.ACTION_CANCEL | (event.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT)); view.onTouchEvent(cancelEvent); cancelEvent.recycle(); } if (swiping) { translationX = deltaX; view.setTranslationX(deltaX - swipingSlop); callback.onSwipe(); return true; } break; } } return false; } }